js 上下文总结

October 23, 2018

01 context

任何方法,谁调用了它,则就是它的 context。

这个和 this 的描述是一致的:

this 最终指向的是调用它的对象

上下文就是 this 的指向。


02 不稳定的 context

context 在 js 的表现经常出现一些看似”不稳定”的情况,下面列出了一些”不稳定”。

(1) new 和非 new

下面是一道常见的面试题,问 ① 和 ② 分别输出什么结果?

function say() {
  console.log(this)
}

new say() // ①
say() // ②

虽然简单,却是对 context 最全的理解。 ① 输出 say 的实例 ②输出 window 吗? 显然我们凭借这段代码是无法做出对 ② 的判断,因为我们根本不知道 say()的运行环境是什么。


(2) 方法赋值

下面试题,将方法赋值给一个变量。

var name = "嗷嗷嗷"
var cat = {
  name: "喵",
  say: function () {
    console.log(this.name)
  },
}
var say = cat.say
cat.say() // ①
say() // ②

结果: ① 输出 喵 ② 输出 嗷嗷嗷

这里可以理解:因为 say 其实只是引用了一个函数,函数的运行依赖 context,不同的 context 必然有不同结果。


(3) Event Loop

Event Loop 又叫事件循环,由于 js 没有多线程,处理多个任务的时候,就需要采用异步队列(如定时器、I/O、Promse),或者利用浏览器的多线程启动多个任务(如 ajax)。

那么异步任务,它的 context 就是浏览器当前打开的窗口了,即 window。

下面是一个常见的面试问题:

function say() {
  setTimeout(function () {
    console.log(this)
  }, 0)
}
new say() //①
say() //②

① 和 ② 都输出 window。


(4) 自执行函数

看下面一个问题:

function say() {
  ;(function () {
    console.log(this)
  })()
}
new say() //①
say() //②

① 和 ② 都输出 window,这里也说明了自执行函数是被 window 调用。


03 闭包拯救世界

面对不稳定的 context,闭包可以被动解决这些问题。 因为我们所期望的 context,和闭包所表现的 scope 惊人相似。

于是我们纷纷这样做:

function say() {
  var _this = this
  setTimeout(function () {
    console.log(_this)
  }, 0)
}
new say() //①
say() //②
function say() {
  ;(function (_this) {
    console.log(_this)
  })(this)
}
new say() //①
say() //②

上面做法用_this 代替 this,使得正常访问外层 this。


04 bind 来优化

对于用闭包来改变 context 的问题,不是很优雅。 用 bind 会更简洁。

实现是下面代码:

function say() {
  setTimeout(
    function () {
      console.log(this)
    }.bind(this),
    0
  )
}
new say() //①
say() //②

通过 bind 直接改变当前函数的 context,这样子做法是符合我们的阅读习惯的。


05 call 和 apply

call 和 apply 又叫对象冒充,是在方法执行的时候,传入一个对象,顶替原有的 context。

下面代码通过 call,主动改变的执行函数的 context。这在封装里面很重要。

<div onclick="clickdiv.call(this)">click me</div>
<script type="text/javascript">
  function clickdiv() {
    console.log(this)
  }
</script>

点击 div,打印 div 元素。


06 箭头函数

可能意识到 context 这个问题,于是 es6 推出箭头函数。箭头函数采用词法作用域,这使得它和闭包的解决极为相似。

注意:arguments 本身在箭头函数下,会指向外层函数的 arguments (同 this 一样)。

function say() {
  setTimeout(() => {
    console.log(this)
  }, 0)
}
new say() //①
say() //②

这里 ① 和 ② 都正常输出了。


07 Arguments

Arguments 也是 context 绕不过去的坎,Arguments 是函数的内部对象,js 在执行函数的时候,会根据 Arguments 初始化函数内部的变量。

arguments.callee 可以访问到当前函数。 arguments.callee.caller 可以访问调用当前函数的函数。

arguments 上面特性严格模式下是不允许被使用的。


Profile picture

Written by Vance who lives and works in Shenzhen, China, and is working hard to improve. You should follow them on csdn